/*************************************************************************
	> File Name: DHT11.c
	> 作者:YJK
	> Mail: 745506980@qq.com
	> Created Time: 2021年05月20日 星期四 21时33分51秒
************************************************************************/
/*************************************************************************
	> File Name: DHT11.c
	> 作者:OJT
	> Created Time: 2024-06-14
************************************************************************/
#include<linux/init.h>
#include<linux/module.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/delay.h>  //内核延时 忙等
#include <linux/uaccess.h>
#include <linux/gpio.h> // 包含 GPIO 支持

#define ERROR_PRINT(x) do{	\
		printk(KERN_ERR x " is error!\n"); \
		return -1; \
	}while(0)

#define DEVICE_NAME "dht11"

#define HIGH 1 // 代表高电平
#define LOW 0 // 代表低电平

#define DHT_MAXCOUNT 32000 // 计数超时的最大值

// dht11响应信号 + 数据信号，一共 1 + 40 次低高电平变化
#define DHT_PULSES 41
// 低高电平变化中，低和高各变化一次，所以电平维持计数记录需要 (1 + 40) * 2 次
#define DHT_PULSES_SAMPLING_COUNT (DHT_PULSES * 2)

struct gpio_desc* dht11_gpiod; //DHT11_GPIO编号

struct dht11_info {
	dev_t devt; // 实际上就是 u32 类型 用于记录主设备号
	struct miscdevice dht11_device; // misc设备 用于创建 /dev/ 下的节点
};

struct dht11_info dht11;

/*
 * dht11 采样时序
 * 1. 起始信号
 * 主机发送采集信号，此时 GPIO 由 CPU 控制，设置为输出状态
 * 主机由高电平-->低电平(至少18ms)--->高电平(20-40us)
 *
 * 2. DHT响应信号，此时 GPIO 由 dht11 控制，所以应该设置为输入状态
 * 低电平(80us)--->高电平(80us)
 *
 * 3. DHT数据传输信号，共40bit，即5字节数据开始传输，先发送的bit是字节里的高位（MSB）
 * 数据开始  低电平(50us) ---> 数据0  高电平 26us-28us
 * 数据开始  低电平(50us) ---> 数据1  高电平 70us
 */

static void end(void)
{
	// 建议结束时，GPIO输出高，下次采样成功率高点
	gpiod_direction_output(dht11_gpiod, HIGH);
}

ssize_t dht11_read(struct file *file, char __user * user_buf, size_t size, loff_t * loff)
{
	int err;
	unsigned char dht11_data[5];
	unsigned int pulse_counts[DHT_PULSES_SAMPLING_COUNT];
	volatile unsigned int i;
	unsigned int pulse_low_average;
	int data_index;

	/*
	 * 下列算法解析：
	 *
	 * DHT_PULSES_SAMPLING_COUNT 为 82，因为从DHT11的响应信号开始，低高电平一共有82次
	 * 低 80us 高 80us 为2次，这是一开始的响应信号
	 * 低 50us 高 28us 或 70us，表示bit为0或1，这个有40次信号
	 * 所以 (1 + 40) * 2 = 82
	 * 数组输出化 0，因为这个数组是将电平维持时间通过计数形式表达
	 * 如果电平维持时间为 80us，那么 pulse_counts[i] 就会比较大
	 * 如果电平维持时间为 28us，那么 pulse_counts[i] 就会相比于80us时比较小
	 *
	 * 对于代表 低电平维持了 50us 的那些pulse_counts[i]而言（从i=2，i += 2）
	 * 由于维持的时间都相同，所以计数值都差不多，把这40个低电平50us的计数求平均数
	 * 那么这个平均数就代表 50us 的计数
	 * 当分辨bit是0还是1时，就需要看低电平50us后的高电平维持的时间，也就是计数值。
	 * 当计数值小于上面求的平均数，代表着高电平维持的时间少于50us，也就是在了 28us 左右，那么当前bit为0。
	 * 当计数值大于上面求的平均数，代表着低电平维持的时间多于50us，也就是在了 70us 左右，那么当前bit为1。
	 *
	 * 这样就能得到5个字节的数据。
	 */
	for (i = 0; i < DHT_PULSES_SAMPLING_COUNT; ++i)
		pulse_counts[i] = 0;
	for (i = 0; i < 5; ++i)
		dht11_data[i] = 0;

	// 实现 1. 起始信号 的时序
	gpiod_direction_output(dht11_gpiod, LOW);
	mdelay(20); //拉低20ms 要求是(最小18ms)
	gpiod_direction_output(dht11_gpiod, HIGH);
	udelay(40); //拉高40us 要求是(20-40us)

	// 之后是DHT11响应信号的数据传输信号，所以 GPIO 要设置为输入状态
	gpiod_direction_input(dht11_gpiod);

	err = 0;
	for (i = 0; i < DHT_PULSES_SAMPLING_COUNT; i += 2) {
		// 根据前面所说，响应信号和数据传输信号，都是以低电平开始，高电平结束
		// 这里就是前面说的计数，将电平维持时间转化为计数值
		while(gpiod_get_value(dht11_gpiod) == LOW) {
			if (++pulse_counts[i] >= DHT_MAXCOUNT) {
				// failed
				err = 1;
				break;
			}
		}
		if (err)
			break;
		while(gpiod_get_value(dht11_gpiod) == HIGH) {
			if (++pulse_counts[i + 1] >= DHT_MAXCOUNT) {
				// failed
				err = 1;
				break;
			}
		}
		if (err)
			break;
	}

	// 计数过大（DHT_MAXCOUNT），代表DHT11数据信号采样（计数）失败
	if (err) {
		// 计数失败会很常见，所以这个打印可以注释掉
		pr_info("dht11 lost response! (GPIO : %d)\n", desc_to_gpio(dht11_gpiod));
		end();
		return -16;
	}

	/*
	 * 开始统计收集到的计数值，此时已经和DHT11没关系了
	 */

	// 跳过一开始的 80us 低 80us 高的响应信号 所以 i 不会等于0和1

	// 算出低电平的维持50us时的平均值
	pulse_low_average = 0;
	for (i = 2; i < DHT_PULSES_SAMPLING_COUNT; i += 2)
		pulse_low_average += pulse_counts[i];
	pulse_low_average /= DHT_PULSES; //平均值

	// 根据平均值，区分 bit 0 还是 bit 1
	for (i = 3; i < DHT_PULSES_SAMPLING_COUNT; i += 2) {
		/*
		 * 3  5  7  9  11 13 15 17
		 * 19 21 23 25 27 29 31 33
		 * 35 37 39 41 43 45 47 49
		 * 51 53 55 57 59 61 63 65
		 * 67 69 71 73 75 77 79 81
		 *
		 * 要算当前的 pulse_counts[i] 属于那个字节，也就是 data_index
		 * i从3开始，所以先减去3
		 * 8bit 一个字节，但是 pulse_counts[i - 1]  pulse_counts[i] 构成一个bit
		 * 所以 8 * 2 = 16，所以要除以16，这样就算到了 data_index
		 */
		data_index = (i - 3) / 16;
		dht11_data[data_index] <<= 1; // 前面说过，先来的bit属于高位，所以先左移一位，将高位向前移动

		// 高于平均值代表是 bit1
		if (pulse_counts[i] >= pulse_low_average)
			dht11_data[data_index] |= 1;
		// 由于前面已经左移一位，所以第 0 位一定是 0，所以遇到bit0，无需再置位
	}

	if (copy_to_user(user_buf, dht11_data, size) != 0)
		ERROR_PRINT("dht11 date copy_to_user failed");
	end();
	return 0;
}

ssize_t dht11_write(struct file *file, const char __user *user_buf, size_t size, loff_t *loff)
{
	int ret;
	unsigned int gpio;
	struct gpio_desc* new_gpiod_desc;

	// 检查用户空间的buffer是否足够大以存储一个无符号整型也就是4字节
	if (size < sizeof(gpio)) {
		printk(KERN_WARNING "dht11_write: buffer too small\n");
		return -EINVAL;
	}

	// 从用户空间复制数据到内核空间 不能直接 memcpy
	ret = copy_from_user(&gpio, user_buf, sizeof(gpio));
	if (ret != 0) {
		printk(KERN_ERR "dht11_write: failed to copy data from user space\n");
		return -EFAULT;
	}

	// 根据 GPIO 编号获取 gpiod_desc
	new_gpiod_desc = gpio_to_desc(gpio);
	if (new_gpiod_desc < 0)
		return -1;

	dht11_gpiod = new_gpiod_desc; // 更新 gpiod_desc 之后 read 调用时就会操作新的gpio

	/* 返回写入的字节数 */
	return sizeof(gpio);
}

int dht11_open(struct inode * inode, struct file * file)
{
	return 0;
}

int dht11_release(struct inode * inode, struct file *file)
{
	return 0;
}

/*
 * 因为最终目的是为了对 /dev/dht11 节点进行io
 * 所以需要实现这些内核接口，实现了才能进行操作
 * open 和 release 对应 open 和 close 函数，但没有什么初始化和关闭操作，所以函数里面无需实现代码
 *
 * read 对应 read 函数，是为了读取5个字节的采样数据，也就是触发一次采样
 *
 * write 对应 write 函数，对于dht11而言，没什么要写入的，但是本驱动实现的是改变操作dht11的gpio的效果
 * 比如一开始设备树里面注册的是gpio 88去控制的，但是实际使用时，改成了gpio 61去控制
 * 那么write写入61这个数字所在int的指针，即可修改gpiod_desc(dht11_gpiod)为 gpio 61 的gpiod_desc
 * 后续的read函数也是操作gpio 61这个引脚。
 */
struct file_operations fops = {
	.open = dht11_open,
	.release = dht11_release,
	.read = dht11_read,
	.write = dht11_write,
	.owner = THIS_MODULE
};

/*
 * 字符设备管理代码
 * 下面的代码是为了向内核注册驱动和设备
 *
 * 这两个是为了注册驱动和注销驱动
 * dht11_init
 * dht11_exit
 *
 * 这两个是为了声明设备和删除设备
 * dht11_probe
 * dht11_remove
 *
 * 有了驱动，才会去找设备
 * 而找到设备靠的是设备树里面有没有一个设备的 compatible 字段和
 * of_match_table 里面 compatible 字段匹配
 * 如果匹配了，就会调用 dht11_probe
 */

int dht11_probe(struct platform_device *pdev)
{
	int ret;

	/*
	 * dht11-gpios = <&gpio 61 GPIO_ACTIVE_HIGH>;
	 * 注意 GPIO_ACTIVE_HIGH 这个也要关注
	 * 如果 GPIO的高电平对应逻辑值 1，那么填 GPIO_ACTIVE_HIGH
	 * 否则 填GPIO_ACTIVE_LOW
	 */
	dht11_gpiod = devm_gpiod_get_optional(&pdev->dev, DEVICE_NAME, 0);
	if (dht11_gpiod <= 0)
		ERROR_PRINT("devm_gpiod_get_optional");

	/*注册字符设备驱动*/
	// 分配设备号
	ret = alloc_chrdev_region(&dht11.devt, 0, 1, DEVICE_NAME);
	if (ret != 0)
		ERROR_PRINT("alloc_chrdev_region");

	// 注册 misc 设备，在/dev/下生成 dht11 节点（文件）
	dht11.dht11_device.minor = MISC_DYNAMIC_MINOR; // 动态分配次设备号
	dht11.dht11_device.name = "dht11";
	dht11.dht11_device.fops = &fops;
	ret = misc_register(&dht11.dht11_device); // 这样 /dev 下就会生成对应节点
	if (ret < 0) {
		unregister_chrdev_region(dht11.devt, 1);
		return ret;
	}

	printk(KERN_INFO "dht11 probe success! (GPIO: %d)\n", desc_to_gpio(dht11_gpiod));
	return 0;
}

int dht11_remove(struct platform_device *pdev)
{
	// 注销 misc 设备
	misc_deregister(&dht11.dht11_device);
	// 注销设备号
	unregister_chrdev_region(dht11.devt, 1);
	// 释放 GPIO 描述符
	gpiod_put(dht11_gpiod);
	return 0;
}

struct of_device_id of_match_table[] = {
	{.compatible = "dht11-sensor"},
	{}
}; // 设备树匹配

struct platform_driver pdrv = {
	.probe = dht11_probe,
	.remove = dht11_remove,
	.driver = {
		.name = DEVICE_NAME,
		.owner = THIS_MODULE,
		.of_match_table = of_match_table
	},
};

static int dht11_init(void)
{
	// 注册platform_driver
	int ret = platform_driver_register(&pdrv);
	if (ret != 0)
		ERROR_PRINT("platform driver register");

	return 0;
}

static void dht11_exit(void)
{
	/*注销platform_driver*/
	platform_driver_unregister(&pdrv);
}

module_init(dht11_init);
module_exit(dht11_exit);

MODULE_LICENSE("GPL");
